package bancosys.tec.persist.base.enumx;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import jmine.tec.persist.PersistMessages;

/**
 * Utilitario para Enums. Trata enums java e apache commons de forma transparente.
 * 
 * @author Marcio Ribeiro
 * @date May 18, 2008
 */
public final class EnumUtils {

    /**
     * Classe utilitaria, nao deve ser instanciada.
     */
    private EnumUtils() {
        // do nothing
    }

    /**
     * Devolve enum java para o valor passado.
     * 
     * @param <E> tipo de enum.
     * @param enumClass classe de enum.
     * @param value valor.
     * @return enum.
     */
    public static <E extends Enum<E>> E getEnumByValue(final Class<E> enumClass, final Object value) {
        try {
            if (value == null) {
                throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NOT_FOUND.create(enumClass, "null"));
            }

            E[] values = getValues(enumClass);

            final String asString = value.toString();
            for (E v : values) {
                if (isEnumConstant(v, value) || v.equals(value) || v.name().equalsIgnoreCase(asString)
                        || v.toString().equalsIgnoreCase(asString)) {
                    return v;
                }
            }
        } catch (final IllegalAccessException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_ILLEGAL_ACCESS.create(), e);
        } catch (final InvocationTargetException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_INVOCATION_EXCEPTION.create(), e);
        } catch (final NoSuchMethodException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NO_SUCH_METHOD.create(), e);
        }
        throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NOT_FOUND.create(enumClass, value));
    }

    /**
     * Diz se o valor representa uma constante de enum ou não
     * 
     * @param enumx enum de um determinado tipo
     * @param value valor a ser verificado se representa uma constante ou não
     * @return se é um valor do enum ou não
     */
    private static boolean isEnumValue(org.apache.commons.lang.enums.Enum enumx, Object value) {
        if (enumx instanceof StringValuedEnum) {
            StringValuedEnum en = (StringValuedEnum) enumx;
            if (en.getValue().equals(value) || (en.getValue().equalsIgnoreCase(value.toString()))) {
                return true;
            }
        } else if (enumx instanceof IntValuedEnum) {
            IntValuedEnum en = (IntValuedEnum) enumx;
            if (value instanceof Integer) {
                return en.getValue() == (Integer) value;
            }
            if (value instanceof String) {
                try {
                    Integer v = Integer.parseInt((String) value);
                    if (en.getValue() == v) {
                        return true;
                    }
                } catch (NumberFormatException e) {
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * Diz se o valor representa uma constante de enum ou não
     * 
     * @param enumx enum de um determinado tipo
     * @param value valor a ser verificado se representa uma constante ou não
     * @return se é uma constante ou não
     */
    @SuppressWarnings("unchecked")
    private static boolean isEnumConstant(final Enum enumx, final Object value) {
        try {
            Enum en = Enum.valueOf(enumx.getClass(), value.toString());
            return en != null && en.equals(enumx);
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    /**
     * Devolve o enum apache para o valor passado.
     * 
     * @param <E> tipo de enum.
     * @param enumClass classe de enum.
     * @param value valor.
     * @return enum.
     */
    public static <E extends org.apache.commons.lang.enums.Enum> E getApacheEnumByValue(final Class<E> enumClass, final Object value) {
        try {
            if (value == null) {
                throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NOT_FOUND.create(enumClass, "null"));
            }

            List<E> values = getApacheValues(enumClass);

            for (E v : values) {
                if (isEnumValue(v, value) || v.equals(value) || v.getName().equals(value) || v.toString().equals(value.toString())) {
                    return v;
                }
                if (v.getName().equalsIgnoreCase(value.toString()) || v.toString().equalsIgnoreCase(value.toString())) {
                    return v;
                }
            }
        } catch (final IllegalAccessException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_ILLEGAL_ACCESS.create(), e);
        } catch (final InvocationTargetException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_INVOCATION_EXCEPTION.create(), e);
        } catch (final NoSuchMethodException e) {
            throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NO_SUCH_METHOD.create(), e);
        }
        throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NOT_FOUND.create(enumClass, value));
    }

    /**
     * Devolve array de valores para classe de enum java passada.
     * 
     * @param <E> tipo de enum.
     * @param enumClass classe de enum.
     * @return array com enums para classe passada.
     * @throws IllegalAccessException caso nao consiga executar metodo para trazer valores de enum.
     * @throws InvocationTargetException caso nao consiga executar metodo para trazer valores de enum.
     * @throws NoSuchMethodException caso nao consiga executar metodo para trazer valores de enum.
     */
    @SuppressWarnings("unchecked")
    private static <E extends Enum> E[] getValues(final Class<E> enumClass) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        return (E[]) enumClass.getMethod("values").invoke(null);
    }

    /**
     * Devolve lista de valores para classe de enum apache passada.
     * 
     * @param <E> tipo de enum.
     * @param enumClass classe de enum.
     * @return lista com enums para classe passada.
     * @throws IllegalAccessException caso nao consiga executar metodo para trazer valores de enum.
     * @throws InvocationTargetException caso nao consiga executar metodo para trazer valores de enum.
     * @throws NoSuchMethodException caso nao consiga executar metodo para trazer valores de enum.
     * @deprecated utilize getValues
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public static <E extends org.apache.commons.lang.enums.Enum> List<E> getApacheValues(final Class<E> enumClass)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        final String methodName = "getEnumList";
        Method method = org.apache.commons.lang.enums.Enum.class.getDeclaredMethod(methodName, Class.class);
        method.setAccessible(true);
        List<E> result = (List<E>) method.invoke(null, enumClass);
        if (result.isEmpty()) {
            method = enumClass.getDeclaredMethod(methodName);
            result = (List<E>) method.invoke(null);
        }
        return result;
    }

    /**
     * Devolve a lista de valores para classe de enum apache passada.
     * 
     * @param <E> tipo de enum.
     * @param enumClass classe de enum.
     * @return lista com enums para classe passada.
     */
    public static <E extends org.apache.commons.lang.enums.Enum> List<E> getApacheEnumValues(Class<E> enumClass) {
        // Força a inicialização da classe.
        try {
            Class.forName(enumClass.getName(), true, enumClass.getClassLoader());
        } catch (ClassNotFoundException e) {
            // nao deveria ser lancada
        }
        try {
            final String methodName = "getEnumList";
            Method method = org.apache.commons.lang.enums.Enum.class.getDeclaredMethod(methodName, Class.class);
            method.setAccessible(true);
            List<E> result = new LinkedList<E>();
            for (Object e : (List<?>) method.invoke(null, enumClass)) {
                result.add(enumClass.cast(e));
            }
            if (result.isEmpty()) {
                Method alternative = enumClass.getDeclaredMethod(methodName);
                for (Object e : (List<?>) alternative.invoke(null, enumClass)) {
                    result.add(enumClass.cast(e));
                }
            }
            return result;
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException(e);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }
}
